interface Pair<K,V>{
    Key:K,
    Val:V
};

const SET=<K,V>(key:K,val:V):Pair<K,V>=>{return {Key:key,Val:val}}

export default class Map<K, V>{
    private _iterable:Pair<K,V>[] = [];
    private _findKey:K = null;

    public get size():number{return this._iterable.length;}

    public get iterable():Pair<K,V>[]{return this._iterable;}

    public inset(key:K,val:V):V{
        const it:Pair<K,V> = this._find(key);
        if (it){
            // @ts-ignore
            it.Val = Array.isArray(it.Val) ? val.slice(0): val;
            return val;
        }
        
        this._iterable.push(SET(key,val));
        return this._find(key).Val;
    }

    public remove(key:K):number{
        let idx:number = -1;
        for (let it of this._iterable){
            if (it.Key === key){
                this._iterable.splice(idx,1);
                return this._iterable.length;
            }
            idx++;
        }
        return -1;
    }

    public sort(cb:(a:Pair<K,V>,b:Pair<K,V>)=>number):Array<Pair<K,V>>{
        const _sort = ():number=>{return 0;}
        this._iterable.sort(cb || _sort);
        return this._iterable;
    }

    public find(key:K):V{
        const it:Pair<K,V> = this._find(key);
        return it ? it.Val : null;
    }

    public clear():void{
        this._iterable = [];
    }

    public next(key:K,stop?:(Key:K,Val:V)=>boolean):Pair<K,V>{
        if (!key || this._iterable.length < 1) return null;

        if (!this._findKey) this._findKey = key;
        let idx = 0;
        for (idx = 0; idx < this._iterable.length; ++idx) if (this._iterable[idx].Key === key) break;
        
        const item = this._iterable[(idx + 1) % this._iterable.length];

        const isStop = (item:Pair<K,V>):boolean=>{
            return item !== null ? (stop ? stop(item.Key,item.Val) : item.Key === this._findKey):true;
        }
        
        if (isStop(item)){
            this._findKey = null;
            if (item.Key === this._findKey){
                return this._iterable[(idx - 1) % this._iterable.length];
            }
            return item;
        }

        return this.next(item.Key);
    }

    public back(key:K,stop?:(key:K,val:V)=>boolean):Pair<K,V>{
        let first = this._iterable.length -1;
        if (!key || first < 0) return null;

        if (!this._findKey) this._findKey = key;
        
        for (; first > 0; --first){
            if (this._iterable[first].Key === key) break ;
        }
        let idx = first-1 < 0 ? this._iterable.length-1:first-1;
        let item =  this._iterable[idx % this._iterable.length]; 
        const isStop = (item:{Key:K,Val:V}):boolean=>{
            return item !== null ? (stop ? stop(item.Key,item.Val) : item.Key === this._findKey):true;
        }
        
        if (isStop(item)){
            this._findKey = null;
            if (item.Key === this._findKey){
                return this._iterable[(idx + 1) % this._iterable.length];
            }
            return item;
        }

        return this.back(item.Key);
    }

    public forEach(cb:(item:Pair<K,V>,index?:number)=>void){
        this._iterable.forEach((it:Pair<K,V>,i:number)=>{cb && cb(it,i)});
    }

    public valueToArray():any[]{
        let out:any[] = [];
        for (let it of this._iterable){
            if (Array.isArray(it.Val)){
                for (let sub of it.Val) out.push(sub);
            } else out.push(it);
        }
        return out;
    }

    private _find(key:K):Pair<K,V>{
        const half = Math.floor((this._iterable.length - 1)>>1);
        if (half < 0) return null;
        const tmp = this._iterable[half];
        if (tmp.Key === key) return tmp;

        for (let n=half,b=half; n < this._iterable.length; ++n,--b){
            const next = this._iterable[n+1];
            const back = b > 0 ? this._iterable[b-1]:null;
            if (next && next.Key === key) return next;
            if (back && back.Key === key) return back;
        }
        return null;
    }
}